До сих пор в программных примерах мы пользовались только функциями стандартной библиотеки С. Однако в C++ имеются собственные средства, основанные на принципах классовой модели. Другими словами, в исполнительной библиотеке C++ имеется набор классов для управления вводом-выводом.
В отличие от функций буферизованного ввода-вывода С (таких, как print f и scanf, не выполняющих никаких проверок на соответствие аргументов форматной строке) классы потоков C++ безопасны в отношении типа. Ввод-вывод использует механизм перегрузки операций, гарантирующий вызов нужной функции-операции для указанного типа данных. Это главное преимущество потоков языка C++.
Классы потоков
К классам потоков относятся следующие:
Для работы с потоками вам потребуется включить в программу заголовочный файл iostream.h. Кроме того, может потребоваться подключить файлы fstream.h (файловый ввод-вывод), iomanip.h (параметризованные манипуляторы) и strstream.h (форматирование ь памяти).
Предопределенные потоки
Библиотека ввода-вывода C++ предусматривает четыре предопределенных объекта-потока, связанных со стандартными входным и выходным устройствами. Ниже дана сводка этих объектов.
Таблица 9.1. Предопределенные объекты-потоки C++
Имя |
Класс |
Описание |
cin |
istream |
Ассоциируется со стандартным вводом (клавиатурой). |
cout |
ostream |
Ассоциируется со стандартным выводом (экраном). |
cerr |
ostream |
Ассоциируется со стандартным устройством ошибок (экраном) без буферизации. |
clog |
ostream |
Ассоциируется со стандартным устройством ошибок (экраном)с буферизацией. |
Операции извлечения и передачи в поток
Основными классами ввода-вывода C++ являются istream и ostream. Первый из них перегружает операцию правого сдвига (>>), которая служит в нем для ввода данных и называется операцией извлечения из потока. Класс ostream перегружает соответственно операцию левого сдвига (<<); она применяется для вывода и называется операцией передачи в поток.
Нужно сказать, что стандартной русской терминологии как таковой в C++ не существует. Каждый изобретает свою собственную; иногда удачно, иногда — нет.
Вот простейшие операторы ввода и. вывода на стандартных потоках:
#include <iostream.h>
int main()
{
char name [ 8.0] ;
cout<< "Enter your name: ";
cin>> name;
cout <<"Hello " << name << "!";
return 0;
}
Как видите, при действиях над потоками возможно последовательное сцепление операций, подобно последовательному присваиванию. Как вы уже знаете, такая форма записи обеспечивается благодаря тому, что функции-операции извлечения и передачи возвращают ссылку на свой объект.
Перегруженные операции для встроенных типов
Классы istream и ostream перегружают операции соответственно извлечения и передачи в поток для всех встроенных типов. Это позволяет единообразно применять эти операции для чтения и вывода символов, целых, вещественных чисел (т. е. с плавающей точкой) и строк. Вот небольшая иллюстрация, где попутно показан еще простейший прием проверки на ошибку при вводе:
#include <iostream.h>
void check(void) {
if (!cin.good())
{
// Либо просто if (!cin) {
cout << "Error detected!";
exit (1);
}
int main(void)
{
double d;
long 1;
cout << "Enter a floating point value: ";
cin >> d;
check () ;
cout << "You entered: " << d << '\n';
cout << "Enter an integer value: ";
cin >> 1;
check () ;
cout << "You entered: " << 1 << '\n';
return 0;
}
Операции извлечения и передачи в поток (соответственно для классов istream и ostream) можно перегрузить таким образом, чтобы можно было применять их для ввода или вывода объектов класса, определенного пользователем. Приведенный ниже пример демонстрирует эту методику. Вообще-то в подобных случаях совершенно необходимо предусмотреть детектирование и обработку ошибок ввода, но здесь мы этого не сделали.
#include <iostream.h>
class Point { int x, у;
public:
Point(int xx = 0, int yy = 0) {
x = xx; у = yy;
}
friend istream &operator>>(istream&, Points);
friend ostream &operator“(ostream&, Points);
};
istream &operator”(istream &is, Point &p)
//
// При вводе точка представляется просто парой чисел,
// разделенных пробелом.
// is >> р.х > р.у;
return is;
}
ostream &operator<<(ostream &os.Point &p) {
//
// Вывод в виде (х, у).
//
os<< ' ( '<< р. х<< ", "<< р. у<<') ' ;
return os;
}
int main() {
Point р;
cout<< "Enter point coordinates: ";
cin>> р;
cout<< "The point values are " << р;
return 0;
}
Форматирование
Библиотека ввода-вывода предусматривает три способа форматирования: посредством вызова форматирующих функций-элементов, с помощью манипуляторов или путем установки или сброса флагов потока.
Форматирующие функции-элементы
Эти функции являются элементами класса ios и перегружены таким образом, чтобы можно было либо читать, либо устанавливать значение соответствующего атрибута потока. Если аргумент в вызове отсутствует, функция возвращает текущее значение атрибута. Если аргумент указан, функция устанавливает новое и возвращает предыдущее значение атрибута.
long width(long)
Эта функция предназначена для чтения или установки атрибута ширины поля.
char fill(char)
Функция позволяет прочитать или установить текущий символ заполнения.
По умолчанию символ заполнения — пробел.
long precision(long)
Эта функция позволяет прочитать или установить значение атрибута точности, определяющего либо общее число выводимых цифр, либо число цифр дробной части.
Пример
Ниже приводится программа, демонстрирующая форматирование потока с помощью функций-элементов класса ios.
Листинг 9.1. Демонстрация форматирующих функций потока
///////////////////////////////////////////////
// Format.срр: Форматирующие функции-элементы ios.
//
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
#pragma argsused
int main(int argc, char* argv[])
{
//
// Ширина поля при вводе и выводе.
//
cnar sir [16];
cout<< "Enter something: ";
cin.width(16); // Ввод не более 15 символов. cin>> str;
cout.width(32); // Вывести в поле шириной 32. cout<< str<< "\n\n";
//
// Заполняющий символ и ширина поля. Ширина сбрасывается
// после каждой операции, поэтому она устанавливается
// для каждого числа.
//
int h = 7, m = 9, s = 0; // Выводятся в виде hh:mm:ss.
cout.fill('0'); cout << "Time is ";
cout.width (2); cout << h << ' : ' ; cout.width (2) ;
cout<< m<< ' : ' ;
cout.width (2) ;
cout<< s<< ".\n\n";
cout.fill (' '); // Восстановить пробел.
//
// Точность.
//
double d = 3.14159265358979;
float f = 27182.81828;
cout.precision (5);
cout << f << '\n'; . // Выводит "27183" .
cout << d << '\n'; ' // Выводит "3.1416".
cout .precision (4) ;
cout << f << '\n'; // Выводит "2.718е+04".
cout.setf(ios::fixed); // Установить флаг fixed.
cout<< f<<'\n'; // Выводит "27182.8184".
return 0;
}
Манипуляторы
Манипуляторы потоков являются по существу функциями, которые можно вызывать непосредственно в цепочке операций извлечения или передачи в поток. Различают простые и параметризованные манипуляторы. У простых манипуляторов аргументы отсутствуют. Параметризованные манипуляторы имеют аргумент.
Ниже приводится сводка имеющихся манипуляторов, как простых, так и параметризованных. Они Перечислены в алфавитном порядке.
Таблица 9.2. Простые и параметризованные манипуляторы
Манипулятор |
Описание |
dec |
Задает десятичную базу преобразования. |
end1 |
Передает в поток символ новой строки и сбрасывает поток. |
ends |
Передает в поток символ завершающего строку нуля. |
flush |
Сбрасывает выходной поток. |
hex |
Задает шестнадцатеричную базу преобразования. |
lock(ios Sir) |
Блокирует дескриптор файла потока ir. |
oct |
Задает восьмеричную базу преобразования. |
resetiosflags(int f) |
Сбрасывает флаги, биты которых установлены в f. |
setbase(int b) |
Устанавливает базу преобразования (0, 8, 10 или 16). |
setiosflags(int f) |
Устанавливает флаги, биты которых установлены в f. |
setfill(int c) |
Задает символ заполнения (аналогичен функции fiilO). |
setprecision(long p) |
Задает точность (аналогичен функции precision ()). |
setw(iong w) |
Задает ширину поля (аналогичен функции width ()). |
lunlock(ios &ir) |
Разблокирует дескриптор файла для потока ir. |
ws |
Исключает начальные пробельные символы. |
Вот пример использования некоторых манипуляторов (мы создали один свой собственный):
Листинг 9.2. Форматирование с помощью манипуляторов
/////////////////////////////////////////////////
// Manip.cpp: Демонстрация некоторых манипуляторов.
//
#include <iomanip.h>
#pragma hdrstop
#include <condefs.h>
//////////////////////////////////////////////////
// Манипулятор, определенный пользователем - звонок.
//
ostream shell(ostream &os)
{
return os<< '\a';
#pragma argsused
int main(int argc, char* argv[])
{
cout “ bell; // Тестирование манипулятора bell.
//
// Манипуляторы базы преобразования.
//
long 1 = 123456;
cout<< "Hex: "<< hex<< 1<< end1
<<"Oct: "<< oct<< 1<< end1
<< "Dec: " << dec << 1 << end1;
//
// Параметризованные манипуляторы.
//
int h=12, m=5, s=0; // To же, что в примере
// Format.cpp. cout << "The time is " << setfill('0')
<< setw(2) << h << ':'
<< setw(2) << m << ':'
<< setw(2) << s << setfillC ') << end1;
return 0;
}
Как видите, очень несложно определить свой собственный простой манипулятор. Это всего лишь функция, возвращающая ссылку на переданный ей в параметре поток.
Создать параметризованный манипулятор не так просто. Существуют различные способы сделать это, но наиболее очевидный из них — реализация манипулятора через класс эффектора. Идея состоит вот в чем. Нужно определить для манипулятора собственный класс с конструктором, принимающим нужные параметры, •и перегрузить для этого класса операцию передачи (извлечения) соответствующего потока. После этого конструктор можно вызывать в качестве параметризованного манипулятора. Создается временный объект, который выводится в поток перегруженной операцией и удаляется. Ниже показан манипулятор, который выводит в поток свой аргумент типа unsigned в двоичной форме.
#include <iostream.h>
// Класс эффектора.
class Bin {
int val;
public:
Bin(unsigned arg) { val = arg; }
friend ostream &operator“(ostreams. Bin);
};
// Вывод числа в двоичной форме.
ostream &ooerator<<(ostream &os. Bin b) {
int cb = 1; // Контрольный бит для отсчета циклов.
do {
if (b.val <0) // Если val < 0, то старший бит = 1. os << 1;
else
os<< 0;
} while (b.vai<<= 1, cb<<= 1) ;
return os;
}
int main ()
(
unsigned n = Ox00ff0f34;
cout<< "Some binary: "<< Bin(n)<< end1;
return 0;
}
Рис. 9.1 Манипулятор, выводящий свой аргумент в двоичной форме
Форматирующие флаги
Флаги управления форматированием являются битовыми полями, хранящимися в переменной типа fmtflags (псевдоним int). Для их чтения и/или модификации могут применяться следующие функции-элементы класса ics:
Помимо функций, для управления флагами можно пользоваться манипуляторами setiosflags (аналог setf() с одним параметром) и reset-iosflags (аналог unsetf ()).
В таблице 9.3 описаны форматирующие флаги потоков.
Таблица 9.3. Форматирующие флаги класса ios
Флаг |
Описание |
|
internal |
Если установлен, при выводе чисел знак выводится на левом краю поля вывода, а само число выравнивается по правому краю поля. Промежуток заполняется текущим символом заполнения. |
|
dec |
Устанавливает десятичное представление чисел. Принимается по умолчанию. |
|
oct |
Устанавливает восьмеричное представление чисел. |
|
hex |
Устанавливает шестнадцатеричное представление чисел. |
|
showbase |
Если установлен, то при восьмеричном и шестнадцатеричном представлении чисел выводит индикатор основания (0 для восьмеричных и Ох для шестнадцатеричных чисел). |
|
showpoint |
Если установлен, для вещественных чисел всегда выводится десятичная точка. |
|
uppercase |
Если установлен, шестнадцатеричные цифры от А до F, а также символ экспоненты Е выводятся в верхнем регистре. |
|
boolalpfa |
Если установлен, булевы значения выводятся как слова “true/false”. В противном случае они представляются соответственно единицей и нулем. |
|
showpos |
Выводит + для положительных чисел. |
|
scientific |
Если установлен, вещественные числа выводятся в научной (экспоненциальной) нотации. |
|
fixed |
Если установлен, вещественные числа выводятся в десятичном формате (с фиксированной точкой). |
|
unitbuf |
Если установлен, поток сбрасывается после каждой операции передачи. |
Несколько замечаний относительно перечисленных в таблице флагов.
Имена перечисленных выше флагов и других констант принадлежат к области действия класса ios. Вне этого класса нужно либо воспользоваться разрешением области действия (ios : : scientific), либо обращаться к ним, как к элементам существующего объекта (cout. scientific). Мы поедпочитаем первый способ.
Листинг 9.3. форматирующие флаги потоков
////////////////////////////////////////////////////
// Flags.срр: Форматирующие флаги потоков.
//
#include <iostream.h>
#include <iomanip.h> #pragma hdrstop
#include <condefs.h>
#pragma argsused
int main(int argc, char* argv[])
{
//
// Демонстрация флага skipws. Если его сбросить, то при
// наличии начальных пробелов при вводе возникает ошибка.
//
long 1;
cout<< "Enter an integer: ";
cin.unsetf(ios::skipws);
cin >> 1;
if (cin) // При ошибке потока
cin == NULL. cout<< "You entered "<< 1<< endl;
else {
cout << "Incorrect input."<< endl;
cin.clear (); // Обнуление битов ошибки.
} cout<<endl;
//
// Демонстрация флагов основания и знака.
// Задается основание 16, вывод индикатора и знака +.
//
1 = 8191;
cout.setf(ios::hex, ios::basefield);
cout.setf(ios::showbase | ios::showpos);
cout << "hex: " <<1 << oct // Изменим основание
<< " oct: "<< 1 << dec // манипулятором.
<< " dec: " << 1 << endl;
cout << endl;
//
// Демонстрация флагов формата вещественных чисел.
//
double dl = 1.0е9, d2 = 34567.0;
cout <<"Default: " << dl << " "<<d2 << end1;
// Вывод десятичной точки. cout.setf(ios::showpoint);
cout << "Decimal: " << dl<< " " << d2 << endl;
// Нотация с фиксированной точкой.
// Заодно сбросим вывод знака +.
cout.setf(ios::fixed, ios::floatfield | ios::showpos);
cout << "Fixed: " << dl << " " << d2 << endl;
cout<< endl;
//
// Вывод булевых значений как "true/false".
//
bool b = true;
cout.setf(ios::boolalpha) ;
cout << "Boolean values: " << b << '' << !b “ endl;
return 0;
}
Рис. 9.2 Демонстрация флагов форматиоования потока
Состояние потока
Состояние объекта класса ios (и производных от него) содержится в его закрытом элементе _state в виде набора битов. Следующая таблица перечисляет имеющиеся биты состояния потока.
Таблица 9.4. Биты состояния потока
Бит |
Описание |
goodbit |
С потоком все в порядке (на самом деле это не какой-то бит, а 0 — отсутствие битов ошибки). |
eofbit |
Показывает, что достигнут конец файла. |
failbit |
Индицирует ошибку формата или преобразования. После очистки данного бита работа с потоком может быть продолжена. |
badbit |
Индицирует серьезную ошибку потока, связанную обычно с буферными операциями или аппаратурой. Скорее всего, поток далее использовать невозможно. |
Для опроса или изменения состояния потока в классе ios имеется ряд функций и операций.
Функция operator void*() неявно вызывается, если поток сравнивается с нулем (как cin в примере из листинга),
Файловые потоки
Файловые потоки библиотеки ввода-вывода реализуют объектно-ориентированную методику работы с дисковыми файлами. Имеется три класса таких потоков:
Эти классы выводятся соответственно из istream, ostream и iostream. Таким образом, они наследуют все их функциональные возможности (перегруженные операции << и>>” для встроенных типов, флаги форматирования и состояния, манипуляторы и т. д.).
Чтобы работать с файловым потоком, нужен, во-первых, объект потока, а во-вторых, открытый файл, связанный с этим объектом.
Конструирование объекта потока
Каждый из трех классов файловых потоков имеет четыре конструктора.
ifstream () ;
of stream();
fstream () ;
if stream(const char *name,
int mode = ios::in, long prot = 0666);
ofstream(const char *name,
int mode = ios::out, long prot = 0666);
fstream (const char *name, int mode, long prot = 0666);
ifstreamfint file);
ofstream(int file);
fstream (int file) ;
ifstream(int file, char *buf, int len)
of stream(int file, char *buf, int len)
fstream (int file, char *buf, int len)
Режимы открытия файла
Параметр mode, который имеет вторая форма конструктора, задает режим открытия файла. Для значений параметра класс ios определяет символические константы, перечисленные в таблице 9.5.
Таблица 9.5. Константы класса ios для режимов открытия файла
Константа |
Описание |
арр |
Открытие для записи в конец файла. |
ate |
При открытии позиционирует указатель на конец файла. |
binary |
Файл открывается в двоичном (не текстовом) режиме. |
in |
Файл открывается для ввода. |
out |
Файл открывается для вывода. |
trunc |
Если файл существует, его содержимое теряется. |
Константы можно комбинировать с помощью поразрядного OR. Для конструкторов классов if stream и ofstream параметр mode имеет значения по умолчанию — соответственно ios : : in и ios : : out.
Закрытие файла
В классах файловых потоков имеется функция close (), которая сбрасывает содержимое потока и закрывает ассоциированный с ним файл.
Кроме того, деструктор потока автоматически закрывает файл при уничтожении объекта потока.
При ошибке закрытия файла устанавливается флаг failbit.
Примеры файловых потоков
Следующий пример (листинг 9.4) демонстрирует различные режимы и способы открытия потока.
Листинг 9.4. Примеры открытия файловых потоков
/////////////////////////////////////////////////////////
// Filemode.срр: Режимы открытия файлов.
//
#include <f stream .,h>
#include <string.h>
#pragma hdrstop
#include <condefs.h>
char *data[] = {"It's the first line of test data.",
"Second ,line.",
"Third line.",
"That's enough!"};
//
// Функция для распечатки содержимого файла. //
int Print(char *fn) {
char buf[80] ;
ifstream ifs(fn) ;
if (!ifs) {
cout <<fn<< " - Error reading file." << endl;
return -1;
} while (ifs) {
ifs.getline(buf, sizeof(buf)) ;
if (ifs)
cout << buf<< end1;
} return 0;
}
#pragma argsused
int main(int argc, char* argv[])
{
char name[]= "Newfile.txt";
fstream fs(name, ios::in);
if (fs) { // Файл уже существует. cout “ name “ " - File already exists." << endl;
} else { // Создать новый файл.
cout<< name<< " - Creating new file."<< endl;
fs.open(name, ios::out);
for (int i=0; i<3; i++) fs << data[i] << endl;
}
fs.close () ;
cout << end1;
//
// Файл либо уже существовал, либо мы его только что
// создали. Распечатаем его.
// Print(name);
cout << endl;
//
// Допишем строку в конец файла.
// fs.open(name, ios::app);
if (rs) {
fs M<< data[3]<< endl;
fs.close ();
} Print(name);
return 0;
}
Рис. 9.3 Результат работы программы Filemode
Для чтения строки из файла мы применили в программе функцию getline () , которая будет подробно описана чуть позже.
Бесформатный ввод-вывод
До сих пор речь у нас шла почти исключительно о вводе-выводе с использованием операций извлечения/передачи данных. Эти операции перегружены для всех встроенных типов и выполняют соответствующие преобразования из внутреннего представления данных в текстовое и из текстового во внутреннее (машинное).
Однако в библиотеке C++ имеется немало функций бесформатного ввода-вывода, которые часто применяют для чтения и записи двоичных (не-текстовых) файлов.
Двоичный режим ввода-вывода
Двоичный режим открытия файла (с установленным битом binary) означает, что никакой трансляции данных при передаче из файла в поток и обратно производиться не будет. Речь здесь идет не о форматных преобразованиях представления данных. При текстовом режиме (он принимается по умолчанию) при передаче данных между файлом и потоком производится замена пар символов CR/LF на единственный символ LF (' \n ') и наоборот. Это происходит до преобразований представления, которые выполняются операциями извлечения/передачи. Двоичный ввод-вывод означает всего-навсего, что такой замены происходить не будет; тем не менее двоичный режим необходим при работе с сырыми данными, т. е. данными в машинной форме без преобразования их в текстовый формат.
Чтобы открыть файл в двоичном режиме, нужно, как уже упоминалось, установить в параметре mode конструктора потока или функции open() бит ios::binary.
Чтение и запись сырых данных
Чтение сырых данных производится функцией read () класса istream:
istream &read(char *buf, long len);
Здесь buf — адрес буфера, в который будут читаться данные, а len — число символов, которые нужно прочитать.
Запись сырых данных производится функцией write () класса ostream. Она выглядит точно так же, как функция read () :
ostream &write(char *buf, long len);
Здесь buf — адрес буфера, в котором содержатся данные, а len — число символов, которые должны быть записаны в поток.
Обе функции возвращают ссылку на свой объект-поток. Это означает, что возможны их цепные вызовы, т. е. выражения вроде
ostream os (...);
os.write(...).write (...).write(...) ;
Вот небольшой пример записи и чтения сырых данных:
#include <iostream.h>
#include <fstream.h>
int main(void) {
char name[] = "testfile.dat";
int i = 1234567;
double d = 2.718281828;
//
// Открытие выходного потока в двоичном режиме
//и запись тестовых данных.
//
ofstream ofs(name, ios::out | ios::binary);
if (ofs) {
ofs.write((char*)&i, sizeof(i)); // Целое.
ofs.write((char*)&d, sizeof(d)); // Вещественное.
ofs.write(name, sizeof(name)); // Строка. ofs.close ();
}
//
// Открытие входного потока в двоичном режиме.
// if stream ifs(name, ios::in | ios::binary) ;
i = 0; //
d = 0; // Уничтожить данные.
name[0] = '\0'; //
//
// Прочитать данные.
//
if (ifs) {
ifs.read((char*)&i, sizeof (i));
ifs.read((char*)&d, sizeof(d));
ifs.read(name, sizeof(name));
ofs.close () ;
} //
// Проверка - напечатать прочитанные данные. //
cout “ "Data read from file: i = " << i<< ", d = " << d
<< ", name = " << name << endl;
return 0;
}
Некоторые функции потоков
В классах istream и ostream есть ряд функций, которые позволяют выполнять над потоками разные полезные операции (в основном при бесформатном вводе-выводе). Здесь мы опишем наиболее часто употребляемые из них.
Класс istream
Следующие функции являются элементами класса istream:
Устанавливает положение указателя потока. Для первой формы указывается абсолютная, для второй — относительная позиция указателя. Параметр dir может принимать следующие значения:
Класс ostream
Последним двум функциям из istream соответствуют аналогичные функции класса ostream:
Чтение символов и строк
Для чтения одиночных символов, а также строк применяется функция get класса istream. Эта функция перегружена следующим образом:
int get () ;
istream &get(char &c) ;
istream &get(char *buf, long len, char t = '\n');
Две первые формы функции предназначены для извлечения из потока одиночного символа. Функция int get() возвращает символ в качестве своего значения. Функция get (char &c) передает символ в параметре и возвращает ссылку на свой поток.
Вот, например, как можно было бы выполнить посимвольное копирование файлов:
ifstream ifs("infile.dat");
ofstream ofs("outfile.dat");
while (ifs & ofs)
ofs.put(ifs.get());
// put (char) передает в поток
// одиночный символ.
Последняя форма функции get () извлекает из потока последовательность символов. Символы читаются в буфер buf, пока не произойдет одно из следующих событий:
get ():
istream Sgetline(char *buf, long len, char t = '\n');
Разница между этими двумя функциями состоит в том, что get line () извлекает из потока ограничивающий символ, в то время как get () этого не делает. Ни та, ни другая функция не записывает ограничивающий символ в буфер.
Пример использования getline () вы уже видели в листинге 9.4. Вот небольшой пример чтения строк с помощью get ():
#inciude <iostream.h>
int main(void) {
char name[9], ext[4];
cout << "Enter a filename with extension: ";
cin.get(name, 9, '.');
cin.ignore (80, '.'); // Удалить все оставшиеся
// до точки символы. cin.get(ext, 4) ;
cin.ignore(80, '\n'); // Удалить все, что осталось
// в строке.
cout<< "Name: "<< name << "; extension: " << ext << endl;
return 0;
}
Эта программа, как нетрудно догадаться, усекает произвольное имя файла до формата 8.3.
Ввод-вывод с произвольным доступом
Понятие произвольного доступа к файлу подразумевает два, или даже три, различных момента. Во-первых, оно означает, что можно произвольно обращаться к любой записи или любому байту в файле, в противоположность последовательному доступу, когда данные извлекаются или передаются в поток строго по очереди. Во-вторых, предполагается, что на открытом файле можно произвольно чередовать операции чтения и записи. И, наконец, из сказанного вытекает, что ввод-вывод с произвольным доступом является по преимуществу бесформатным.
Приведенная ниже программа открывает (создает новый или переписывает старый) свой файл как двоичный, и, кроме того, сразу для ввода и вывода. Она применяет функции позиционирования потока и функции бесформатного чтения-записи.
Листинг 9.5. Произвольный доступ к файлу
//////////////////////////////////////////////////
// Random.cpp: Демонстрация файла с произвольным доступом.
//
#include <fstream.h>
#include <iomanip.h>
#pragma hdrstop
#include <condefs.h>
const int NP = 10;
const int IS = sizeof(int);
#pragma argsused
int main(int argc, char* argv[])
{
int pt, i;
//
// Открытие файла для чтения/записи.
//
fstream fs("random.pts",
ios::binary | ios::in | ios::out | ios::trunc);
if (ifs) {
cerr << "Failed to open file." << endl;
return (1);
}
//
// Первоначальная запись файла.
//
cout << "Initial data:" << endl;
for (i=0; i<NP; i++){
pt = i;
fs.write((char*)&pt, IS);
cout << setw(4) << pt;
}
cout << endl << endl;
//
// Чтение файла от конца к началу.
//
cout << "Read from the file in reverse order:"<< endl;
for (i=0; i<NP; i++) {
fs.seekg(-(i + 1) * IS, ios::end);
fs.read((char*)&pt, IS);
cout “ setw(4)<< pt; . }
cout<< end1 << end1;
//
// Переписать четные индексы.
//
for (i=l; i<NP/2; i++) {
fs.seekg(2 * i * IS) ;
fs.read((char*)&pt, IS);
pt = -pt;
fs.seekg(fs.tellg () - IS); // Возврат на шаг.
fs.write((char*)&pt, IS);
}
//
// Распечатать файл.
//
cout << "After rewriting the even records:"<<endl;
fs.seekg(0) ;
for (i=0; i<NP; i++) {
fs.read((char*)&pt, IS);
cout << setw(4) << pt;
}
cout << endl;
fs.close ();
return 0;
}
Когда эта программа открывает уже существующий файл, он усекается до нулевой длины (т. е. все его данные теряются). Если вы хотите работать с имеющимися в файле данными, нужно убрать бит ios: :trunc из режима открытия потока. Кстати, в примере это можно сделать безболезненно — данные файла все равно сразу переписываются заново.
В этом примере мы пользовались для позиционирования потока функцией seekg () . Но поскольку поток у нас типа f stream, и открыт он в режиме чтения-записи, то все равно, какую функцию применять для позиционирования — seekg () или seekp () .
He следует упускать из виду, что при выполнении операций бесформатного чтения или записи (read/write) указатель потока сдвигается вперед на число прочитанных (записанных) байтов.
Вывод программы показан на рис. 9.4.
Рис. 9.4 Программа Random
Заключение
Аппарат потоковых классов библиотеки C++ довольно громоздок, если сравнивать его, например, с функциями языка С вроде printf (). Однако средства потоков C++ единообразны, надежны и расширяемы. Как вы узнали из этой главы, можно достаточно просто перегрузить операции извлечения и передачи, чтобы с точки зрения потоков ввода-вывода определенный вами тип выглядел бы подобно встроенным типам данных.
В следующей главе мы займемся шаблонами — средством C++, которое позволяет создавать “обобщенные” классы, служащие моделью некоторого множества конкретных классов.